package core;

import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;

public class VectorialSelectionTree {

	private int mRegionsMatrixOrder = 2;
	
	private int mRegionsDepth = 1;
	
	private Region mRegion;
	
	public VectorialSelectionTree(int depth, double width, double height) {
		Region root = new Region();
		root.x1 = 0;
		root.y1 = 0;
		root.x2 = width;
		root.y2 = height;
		
		createTree(root, depth);
		
		mRegion = root;
	}
	
	public int getRegionsMatrixOrder() {
		return mRegionsMatrixOrder;
	}

	public void setRegionsMatrixOrder(int regionsMatrixOrder) {
		this.mRegionsMatrixOrder = regionsMatrixOrder;
	}

	public int getRegionsDepth() {
		return mRegionsDepth;
	}

	public void setRegionsDepth(int regionsDepth) {
		this.mRegionsDepth = regionsDepth;
	}

	public Region getRegion() {
		return mRegion;
	}
	
	public void addVertices(VectorialObject obj) {
		Node aux;
		int size = obj.getVerticeCount();
		for(int i = 0; i < size; i++) {
			aux = new Node();
			aux.object = obj;
			aux.point = obj.getVertice(i);
			addNode(aux, mRegion);
		}
	}
	
	public void removeVertices(VectorialObject obj) {
		Node aux;
		int size = obj.getVerticeCount();
		for(int i = 0; i < size; i++) {
			aux = new Node();
			aux.object = obj;
			aux.point = obj.getVertice(i);
			removeNode(aux, mRegion);
		}
	}
	
	private void removeNode(Node node, Region root) {
		if(root.isLeaf()) {
			root.removeNode(node);
		}
		else {
			for(Region r : root.getRegions()) {
				if(r.containsPoint(node.point)){
					removeNode(node, r);
					break;
				}
			}	
		}
	}
	
	private void addNode(Node node, Region root) {
		
		if(root.isLeaf()) {
			root.addNode(node);
		}
		else {
			for(Region r : root.getRegions()) {
				if(r.containsPoint(node.point)){
					addNode(node, r);
					break;
				}
			}	
		}
	}
	
	public void updateNode(Node node) {
		removeNode(node, mRegion);
		addNode(node, mRegion);
	}
	
	public List<Node> getIntersectionNodes(Point2D.Double pt, double margenErro) {
		Region r = findRegion(pt, mRegion);
		
		List<Node> selectedNodes = new ArrayList<Node>();
		
		for(Node n : r.getNodes()) {
			if(pt.x >= n.point.x - margenErro && pt.x <= n.point.x + margenErro &&
					pt.y >= n.point.y - margenErro && pt.y <= n.point.y + margenErro){
				selectedNodes.add(n);
			}
		}
		
		return selectedNodes;
	}
	
	
	private Region findRegion(Point2D.Double pt, Region root) {
		
		if(root.isLeaf()) {
			return root;
		}
		
		for(Region r : root.getRegions()) {
			if(r.containsPoint(pt)) {
				return findRegion(pt, r);
			}
		}
		
		return null;
	}
	
	private void createTree(Region root, int depth) {
		
		double dx = Math.abs(root.x1-root.x2)/mRegionsMatrixOrder;
		double dy = Math.abs(root.y1-root.y2)/mRegionsMatrixOrder;
		
		double xOrigen = root.x1;
		double yOrigen = root.y1;
		
		Region topLeft = new Region();
		topLeft.x1 = xOrigen;
		topLeft.y1 = yOrigen;
		topLeft.x2 = xOrigen + dx;
		topLeft.y2 = yOrigen + dy;
		root.addRegion(topLeft);
		
		Region bottonLeft = new Region();
		bottonLeft.x1 = xOrigen;
		bottonLeft.y1 = yOrigen+dy;
		bottonLeft.x2 = xOrigen + dx;
		bottonLeft.y2 = yOrigen+2*dy;
		root.addRegion(bottonLeft);
		
		Region topRight = new Region();
		topRight.x1 = xOrigen+dx;
		topRight.y1 = yOrigen;
		topRight.x2 = xOrigen+2*dx;
		topRight.y2 = yOrigen+dy;
		root.addRegion(topRight);
		
		Region bottonRight = new Region();
		bottonRight.x1 = xOrigen+dx;
		bottonRight.y1 = yOrigen+dy;
		bottonRight.x2 = xOrigen+2*dx;
		bottonRight.y2 = yOrigen+2*dy;
		root.addRegion(bottonRight);
		
		if(depth > 0) {
			depth--;
			createTree(topLeft, depth);
			createTree(bottonLeft, depth);
			createTree(topRight, depth);
			createTree(bottonRight, depth);
		}
	}
	
	public static class Region {
		public double x1;
		public double y1;
		public double x2;
		public double y2;
		private List<Region> mRegions = new ArrayList<Region>();
		private List<Node> mNodes = new ArrayList<Node>();
		
		public boolean isLeaf(){
			return mRegions.size() == 0;
		}
		
		public void addNode(Node node) {
			if(mRegions.size() > 0) {
				throw new IllegalStateException();
			}
			mNodes.add(node);
		}
		
		public void removeNode(Node node){
			mNodes.remove(node);
		}
		
		public void addRegion(Region region) {
			if(mNodes.size() > 0) {
				throw new IllegalStateException();
			}
			mRegions.add(region);
		}
		
		public boolean containsPoint(Point2D.Double pto) {
			return (pto.x >= x1 && pto.x <= x2 && pto.y >= y1 && pto.y <= y2);
		}
		
		public List<Region> getRegions() {
			return mRegions;
		}
		
		public List<Node> getNodes() {
			return mNodes;
		}
	}
	
	public static class Node {
		public Point2D.Double point;
		public VectorialObject object;
	}
}
